package org.cheetah.core.action.spi;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.cheetah.core.action.Action;
import org.cheetah.core.action.ActionContext;
import org.cheetah.core.action.ActionDefinition;
import org.cheetah.core.exchange.Exchange;
import org.cheetah.core.language.CompiledExpression;

@XmlRootElement(name = "switch")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class SwitchDefinition extends ActionDefinition {

	@XmlElement(name = "var", required = true)
	private ExpressionDefinition expression;

	@XmlElement(name = "case", required = false)
	private List<SwitchCaseDefinition> cases;

	@XmlElement(name = "otherwise", required = false)
	private PipelineDefinition otherwise;

	public SwitchDefinition() {
		cases = new ArrayList<SwitchCaseDefinition>();
	}

	public SwitchDefinition var(ExpressionDefinition expression) {
		this.expression = expression;
		return this;
	}

	public SwitchDefinition addCase(SwitchCaseDefinition _case) {
		cases.add(_case);
		return this;
	}

	public SwitchDefinition otherwise(ActionDefinition... actions) {
		otherwise = new PipelineDefinition().addAll(actions);
		return this;
	}

	public SwitchDefinition otherwise(
			Collection<? extends ActionDefinition> actions) {
		otherwise = new PipelineDefinition().addAll(actions);
		return this;
	}

	public Action createAction() throws Exception {
		return new ActionImpl(expression.compile(), cases
				.toArray(new SwitchCaseDefinition[0]), otherwise == null ? null
				: otherwise.createAction());
	}

	private static class ActionImpl implements Action {
		private CompiledExpression exp;

		private SwitchCaseDefinition[] cases;

		private int count;

		private Action otherwise;

		ActionImpl(CompiledExpression exp, SwitchCaseDefinition[] cases,
				Action otherwise) {
			this.exp = exp;

			this.cases = cases;
			this.count = cases.length;

			this.otherwise = otherwise;
		}

		public void process(ActionContext ctx, Exchange exchange)
				throws Exception {
			boolean matched = false;

			Object value = exp.eval(exchange);
			for (int i = 0; i < count; ++i) {
				if (cases[i].matches(value, exchange)) {
					cases[i].process(exchange);

					matched = true;
					break;
				}
			}

			if (!matched && (otherwise != null)) {
				otherwise.process(ActionContext.NOTHING, exchange);
			}

			ctx.process(exchange);
		}
	}
}
